/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using DaveSexton.DocProject.Sandcastle.Configuration;
using System.IO;
using DaveSexton.DocProject.Sandcastle.Resources;
using DaveSexton.Controls;

namespace DaveSexton.DocProject.Sandcastle.ContentManagement
{
	public sealed class XmlContentManager : IDisposable
	{
		#region Public Properties
		public IDocProject Project
		{
			get
			{
				return project;
			}
		}

		public string MediaDirectoryPath
		{
			get
			{
				return mediaDirectoryPath;
			}
		}

		public string MediaDirectoryRelative
		{
			get
			{
				return mediaDirectoryProjectRelative;
			}
		}

		public IEnumerable<XmlContentDocument> Documents
		{
			get
			{
				return documents.Values;
			}
		}

		public XmlContentDocument this[string documentName]
		{
			get
			{
				return documents[documentName];
			}
		}

		public XmlDocumentationDisplayManager DisplayManager
		{
			get
			{
				return displayManager;
			}
		}

		public Importer Importer
		{
			get
			{
				return importer;
			}
		}
		#endregion

		#region Private / Protected
		private readonly IDocProject project;
		private readonly Dictionary<string, XmlContentDocument> documents;
		private readonly string mediaDirectoryPath, mediaDirectoryRelative, mediaDirectoryProjectRelative;
		private readonly XmlDocumentationDisplayManager displayManager;
		private readonly Importer importer;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="XmlContentManager" /> class.
		/// </summary>
		public XmlContentManager(IDocProject project, Presentation presentation, string presentationPath, bool useSandcastleRoot)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			if (presentation == null)
				throw new ArgumentNullException("presentation");

			this.project = project;

			string cssDirectory = (useSandcastleRoot)
				? Path.Combine(Path.Combine(
						SandcastleBuildEngineProvider.Paths.Presentation,
						presentation.Path),
						presentation.CascadingStyleSheetsDirectory)
				: Path.Combine(Path.Combine(
						project.Directory,
						SandcastleBuildEngineProvider.ProjectPaths.RelativeHelpRoot),
						presentation.CascadingStyleSheetsDirectory);

			displayManager = new XmlDocumentationDisplayManager(cssDirectory);

			mediaDirectoryRelative = presentation.MediaDirectory;
			mediaDirectoryProjectRelative = Path.Combine(SandcastleBuildEngineProvider.ProjectPaths.RelativeHelpRoot, mediaDirectoryRelative);
			mediaDirectoryPath = Path.Combine(project.Directory, mediaDirectoryProjectRelative);

			// Rebase example:  ../Art/image.jpg
			// The rebase path is relative from the Html and Html2 directories
			importer = new Importer();
			importer.RebaseDirectory = ".." + PathFormat.MakeVirtual(mediaDirectoryRelative);
			importer.TargetDirectory = mediaDirectoryPath;
			importer.Rebase = ImporterRebaseAction.RebaseDirectory;

			documents = new Dictionary<string, XmlContentDocument>(8, StringComparer.OrdinalIgnoreCase);

			string contentDirectory = (useSandcastleRoot)
				? Path.Combine(Path.Combine(
						SandcastleBuildEngineProvider.Paths.Presentation,
						presentation.Path),
						presentation.ContentDirectory)
				: Path.Combine(presentationPath, presentation.ContentDirectory);

			try
			{
				foreach (string file in Directory.GetFiles(contentDirectory, "*.xml", SearchOption.AllDirectories))
					documents.Add(Path.GetFileName(file), new XmlContentDocument(file, presentation.ContentItemsXPath, presentation.ContentDesignerItems));
			}
			catch (DirectoryNotFoundException) { }

			string sharedContentDirectory = (useSandcastleRoot)
				? SandcastleBuildEngineProvider.Paths.SharedPresentation
				: Path.Combine(Path.Combine(Path.Combine(
						project.Directory,
						SandcastleBuildEngineProvider.ProjectPaths.RelativeHelpRoot),
						SandcastleBuildEngineProvider.ProjectPaths.SharedPresentation),
						presentation.ContentDirectory);

			try
			{
				foreach (string file in Directory.GetFiles(sharedContentDirectory, "*.xml", SearchOption.AllDirectories))
				{
					string name = Path.GetFileName(file);

					if (!documents.ContainsKey(name))
						documents.Add(name, new XmlContentDocument(file, presentation.ContentItemsXPath, presentation.ContentDesignerItems));
				}
			}
			catch (DirectoryNotFoundException) { }
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="XmlContentManager" /> class.
		/// </summary>
		public XmlContentManager(IDocProject project, Presentation presentation, IDocProject importFromProject)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			if (presentation == null)
				throw new ArgumentNullException("presentation");

			if (importFromProject == null)
				throw new ArgumentNullException("importFromProject");

			this.project = project;

			string cssDirectory = Path.Combine(Path.Combine(
						importFromProject.Directory,
						SandcastleBuildEngineProvider.ProjectPaths.RelativeHelpRoot),
						presentation.CascadingStyleSheetsDirectory);

			displayManager = new XmlDocumentationDisplayManager(cssDirectory);

			mediaDirectoryRelative = presentation.MediaDirectory;
			mediaDirectoryProjectRelative = Path.Combine(SandcastleBuildEngineProvider.ProjectPaths.RelativeHelpRoot, mediaDirectoryRelative);
			mediaDirectoryPath = Path.Combine(project.Directory, mediaDirectoryProjectRelative);

			// Rebase example:  ../Art/image.jpg
			// The rebase path is relative from the Html and Html2 directories
			importer = new Importer();
			importer.RebaseDirectory = ".." + PathFormat.MakeVirtual(mediaDirectoryRelative);
			importer.TargetDirectory = mediaDirectoryPath;
			importer.Rebase = ImporterRebaseAction.RebaseDirectory;

			documents = new Dictionary<string, XmlContentDocument>(8, StringComparer.OrdinalIgnoreCase);

			string contentDirectory = Path.Combine(Path.Combine(Path.Combine(
				importFromProject.Directory,
				SandcastleBuildEngineProvider.ProjectPaths.RelativeHelpRoot),
				SandcastleBuildEngineProvider.ProjectPaths.Presentation),
				presentation.ContentDirectory);

			try
			{
				foreach (string file in Directory.GetFiles(contentDirectory, "*.xml", SearchOption.AllDirectories))
					documents.Add(Path.GetFileName(file), new XmlContentDocument(file, presentation.ContentItemsXPath, presentation.ContentDesignerItems));
			}
			catch (DirectoryNotFoundException) { }

			string sharedContentDirectory = Path.Combine(Path.Combine(Path.Combine(
						importFromProject.Directory,
						SandcastleBuildEngineProvider.ProjectPaths.RelativeHelpRoot),
						SandcastleBuildEngineProvider.ProjectPaths.SharedPresentation),
						presentation.ContentDirectory);

			try
			{
				foreach (string file in Directory.GetFiles(sharedContentDirectory, "*.xml", SearchOption.AllDirectories))
				{
					string name = Path.GetFileName(file);

					if (!documents.ContainsKey(name))
						documents.Add(name, new XmlContentDocument(file, presentation.ContentItemsXPath, presentation.ContentDesignerItems));
				}
			}
			catch (DirectoryNotFoundException) { }
		}
		#endregion

		#region Methods
		public int ImportAllResources()
		{
			return ImportAllResources(importer, documents.Values);
		}

		public static int ImportAllResources(Importer importer, System.Collections.IEnumerable documents)
		{
			if (importer == null)
				throw new ArgumentNullException("importer");

			if (documents == null)
				throw new ArgumentNullException("documents");

			int totalCount = 0;

			foreach (XmlContentDocument document in documents)
			{
				int count = ImportAllResources(importer, document);
				totalCount += count;

				if (count > 0)
					document.IsDirty = true;
			}

			return totalCount;
		}

		private static int ImportAllResources(Importer importer, XmlContentDocument document)
		{
			int totalCount = 0;

			foreach (ContentItem item in document.Items)
			{
				if (item.IsDirty)
				{
					string value = item.Value;

					int count = ImportAllResources(importer, ref value);
					totalCount += count;

					if (count > 0)
						item.Value = value;
				}
			}

			return totalCount;
		}

		private static int ImportAllResources(Importer importer, ref string value)
		{
			int count = importer.ImportXhtmlImages(ref value);

			if (count > 0)
				CheckImportErrors(importer);

			return count;
		}

		private static void CheckImportErrors(Importer importer)
		{
			if (importer.HasErrors)
			{
				StringBuilder message = new StringBuilder();
				int i = 0, maxDisplayed = 5;

				foreach (KeyValuePair<string, Exception> error in importer.ImportErrors)
				{
					Log.Exception(error.Value, Resources.Text.ImportResourceError, error.Key);

					if (++i <= maxDisplayed)
						message.AppendFormat("{0} - {1}{2}", error.Value.Message, error.Key, Environment.NewLine);
				}

				if (i > maxDisplayed)
					message.AppendLine("... +" + (importer.ImportErrors.Count - maxDisplayed) + " more");

				DocProjectEnvironment.ShowErrorDialogWithCaption(
					Resources.Text.ImportResourceErrorTitle, Resources.Errors.ImportImagesError, 
					importer.RebaseDirectory, Environment.NewLine, message);
			}
		}

		public void Load()
		{
			foreach (XmlContentDocument document in documents.Values)
				document.Load(importer, delegate(string html, System.Xml.XmlNamespaceManager namespaces)
				{
					return displayManager.ConvertHtmlToXml(html, false, namespaces);
				});
		}

		public void CommitChanges()
		{
			foreach (XmlContentDocument document in documents.Values)
				document.Commit();
		}

		public void CancelChanges()
		{
			foreach (XmlContentDocument document in documents.Values)
				document.Cancel();
		}

		public void SaveChanges()
		{
			foreach (XmlContentDocument document in documents.Values)
			{
				if (document.IsDirty)
				{
					project.GetItem(document.FilePath).Checkout();
					document.Save();
				}
			}
		}

		public void SaveChanges(string path)
		{
			project.GetFolder(path).Checkout();

			foreach (XmlContentDocument document in documents.Values)
			{
				IProjectItem item = project.GetItem(System.IO.Path.Combine(path, System.IO.Path.GetFileName(document.FilePath)));
				item.Checkout();

				document.Save(path);
			}
		}

		public override string ToString()
		{
			// the value returned here is displayed in the property grid
			return string.Empty;
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="XmlContentManager" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="XmlContentManager" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~XmlContentManager()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="XmlContentManager" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		private void Dispose(bool disposing)
		{
			if (disposing && importer != null)
				importer.Dispose();
		}
		#endregion
	}
}
